# Copyright 2024 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Setup code called by the code generated by `local_runtime_repo`."""

load("@bazel_skylib//lib:selects.bzl", "selects")
load("@rules_cc//cc:defs.bzl", "cc_library")
load("@rules_python//python:py_runtime.bzl", "py_runtime")
load("@rules_python//python:py_runtime_pair.bzl", "py_runtime_pair")
load("@rules_python//python/cc:py_cc_toolchain.bzl", "py_cc_toolchain")
load("@rules_python//python/private:py_exec_tools_toolchain.bzl", "py_exec_tools_toolchain")

_PYTHON_VERSION_FLAG = Label("@rules_python//python/config_settings:python_version")

def define_local_runtime_toolchain_impl(
        name,
        lib_ext,
        major,
        minor,
        micro,
        interpreter_path,
        implementation_name,
        os):
    """Defines a toolchain implementation for a local Python runtime.

    Generates public targets:
    * `python_runtimes`: The target toolchain type implementation
    * `py_exec_tools_toolchain`: The exec tools toolchain type implementation
    * `py_cc_toolchain`: The py cc toolchain type implementation
    * `os`: A constraint (or alias to one) for the `target_compatible_with` this
      toolchain is compatible with.
    * `is_matching_python_version`: A `config_setting` for `target_settings`
      this toolchain is compatible with.

    Args:
        name: `str` Only present to satisfy tooling
        lib_ext: `str` The file extension for the `libpython` shared libraries
        major: `str` The major Python version, e.g. `3` of `3.9.1`.
        minor: `str` The minor Python version, e.g. `9` of `3.9.1`.
        micro: `str` The micro Python version, e.g. "1" of `3.9.1`.
        interpreter_path: `str` Absolute path to the interpreter.
        implementation_name: `str` The implementation name, as returned by
            `sys.implementation.name`.
        os: `str` A label to the OS constraint (e.g. `@platforms//os:linux`) for
            this runtime.
    """
    major_minor = "{}.{}".format(major, minor)
    major_minor_micro = "{}.{}".format(major_minor, micro)

    cc_library(
        name = "_python_headers",
        # NOTE: Keep in sync with watch_tree() called in local_runtime_repo
        srcs = native.glob(
            ["include/**/*.h"],
            # A Python install may not have C headers
            allow_empty = True,
        ),
        includes = ["include"],
    )

    cc_library(
        name = "_libpython",
        # Don't use a recursive glob because the lib/ directory usually contains
        # a subdirectory of the stdlib -- lots of unrelated files
        srcs = native.glob(
            [
                "lib/*{}".format(lib_ext),  # Match libpython*.so
                "lib/*{}*".format(lib_ext),  # Also match libpython*.so.1.0
            ],
            # A Python install may not have shared libraries.
            allow_empty = True,
        ),
        hdrs = [":_python_headers"],
    )

    py_runtime(
        name = "_py3_runtime",
        interpreter_path = interpreter_path,
        python_version = "PY3",
        interpreter_version_info = {
            "major": major,
            "micro": micro,
            "minor": minor,
        },
        implementation_name = implementation_name,
    )

    py_runtime_pair(
        name = "python_runtimes",
        py2_runtime = None,
        py3_runtime = ":_py3_runtime",
        visibility = ["//visibility:public"],
    )

    py_exec_tools_toolchain(
        name = "py_exec_tools_toolchain",
        visibility = ["//visibility:public"],
        precompiler = "@rules_python//tools/precompiler:precompiler",
    )

    py_cc_toolchain(
        name = "py_cc_toolchain",
        headers = ":_python_headers",
        libs = ":_libpython",
        python_version = major_minor_micro,
        visibility = ["//visibility:public"],
    )

    native.alias(
        name = "os",
        # Call Label() to force the string to evaluate in the context of
        # rules_python, not the calling BUILD-file code. This is because
        # the value is an `@platforms//foo` string, which @rules_python has
        # visibility to, but the calling repo may not.
        actual = Label(os),
        visibility = ["//visibility:public"],
    )

    native.config_setting(
        name = "_is_major_minor",
        flag_values = {
            _PYTHON_VERSION_FLAG: major_minor,
        },
    )
    native.config_setting(
        name = "_is_major_minor_micro",
        flag_values = {
            _PYTHON_VERSION_FLAG: major_minor_micro,
        },
    )
    selects.config_setting_group(
        name = "is_matching_python_version",
        match_any = [
            ":_is_major_minor",
            ":_is_major_minor_micro",
        ],
        visibility = ["//visibility:public"],
    )
